#include "cellclustermanager.h"
#include "graphics.opengl/renderer.h"
#include "system/timer.h"
#include "graphicsmesh.h"
#include <limits>


#define NEWCELLCLUSTER(size, origin)	new CellClusterCube(size, origin);
//#define NEWCELLCLUSTER(size, origin)	new CellClusterMarchingCube(size, origin);

namespace infinity {
	namespace render {

CellClusterManager::CellClusterManager()
:	m_cameraNode(NULL)
,	m_clusters((VisionRadius * 2 + 1) * (VisionRadius * 2 + 1) * (VisionRadius * 2 + 1))
{
	m_clusters.resize((VisionRadius * 2 + 1) * (VisionRadius * 2 + 1) * (VisionRadius * 2 + 1));

	// reset all clusters priority
	for (ClustersCollection::iterator clusterIt = m_clusters.begin(); clusterIt != m_clusters.end(); ++clusterIt)
	{
		CellClusterHolder& clusterHolder = *clusterIt;

		clusterHolder.cluster = NULL;
		clusterHolder.priority = std::numeric_limits< dw::float32 >::max();
	}
}

CellClusterManager::~CellClusterManager()
{
}

void CellClusterManager::render(dw::graphics::Renderer& renderer)
{
	//opaque
	for (ClustersCollection::iterator clusterIt = m_clusters.begin(); clusterIt != m_clusters.end(); ++clusterIt)
	{
		CellCluster* cluster = clusterIt->cluster;

		if (cluster != NULL && cluster->getModel() != NULL)
		{
			Vector3i clusterOrigin = cluster->getOrigin();
	
			renderer.pushMatrix();
			{
				
				// glTranslatef(clusterOrigin.x, clusterOrigin.y, clusterOrigin.z);

				graphics::Model* model = cluster->getModel();

				renderer.multMatrix(model->getTransform());
				renderer.setVertexBuffer(&(model->getMesh()->getVertexBuffer()));
				renderer.setIndexBuffer(&(model->getMesh()->getIndexBuffer()));
				renderer.drawIndexedPrimitive(dw::graphics::primitive::triangles, 0, model->getMesh()->getIndexBuffer().getCount());
			}
			renderer.popMatrix();
		}
	}
}

void CellClusterManager::update(dw::float32 deltaT)
{
	dwAssert(m_cameraNode != NULL, "Invalid cameraNode");

	Vector3f cameraPos = Vector3f(m_cameraNode->getTransform().pos);
	Vector3i cameraCell;
	Vector3i cameraCluster;

	dw::float32 clusterPriority = 0.0f;

	cameraCell.x = (dw::int32)cameraPos.x - (cameraPos.x < 0.0f ? 1 : 0);
	cameraCell.y = (dw::int32)cameraPos.y - (cameraPos.y < 0.0f ? 1 : 0);
	cameraCell.z = (dw::int32)cameraPos.z - (cameraPos.z < 0.0f ? 1 : 0);

	cameraCluster.x = cameraCell.x / (dw::int32)ClusterSize - ((cameraCell.x < 0) ? 1 : 0);
	cameraCluster.y = cameraCell.y / (dw::int32)ClusterSize - ((cameraCell.y < 0) ? 1 : 0);
	cameraCluster.z = cameraCell.z / (dw::int32)ClusterSize - ((cameraCell.z < 0) ? 1 : 0);

	// update all clusters priority
	for (ClustersCollection::iterator clusterIt = m_clusters.begin(); clusterIt != m_clusters.end(); ++clusterIt)
	{
		CellClusterHolder& clusterHolder = *clusterIt;

		if (clusterHolder.cluster != NULL)
		{
			Vector3i clusterPos = clusterHolder.clusterPos;
			Vector3f clusterCenter = (dw::float32)ClusterSize * Vector3f(clusterPos.x + 0.5f, clusterPos.y + 0.5f, clusterPos.z + 0.5f);

			clusterHolder.priority = (clusterCenter - cameraPos).sqrLength();
		}
		else
		{
			clusterHolder.priority = std::numeric_limits< dw::float32 >::max();
		}
	}

	// sort according to priorities
	std::sort(m_clusters.begin(), m_clusters.end(), clusterPriorityComp);

	// create new clusters
	Vector3i clusterID;

	dw::uint32 generatedClustersCount = 0;

	for (dw::int32 k = -VisionRadius; k < VisionRadius; ++k)
	{
		clusterID.z = cameraCluster.z + k;
		for (dw::int32 j = -VisionRadius; j < VisionRadius; ++j)
		{
			clusterID.y = cameraCluster.y + j;
			for (dw::int32 i = -VisionRadius; i < VisionRadius; ++i)
			{
				clusterID.x = cameraCluster.x + i;
			
				Vector3i clusterPos = clusterID;
				Vector3f clusterCenter = (dw::float32)ClusterSize * Vector3f(clusterPos.x + 0.5f, clusterPos.y + 0.5f, clusterPos.z + 0.5f);

				clusterPriority = (clusterCenter - cameraPos).sqrLength();

				CellClusterHolder* holder = findClusterHolder(clusterID, clusterPriority);

				if (holder && (holder->clusterPos != clusterID || holder->cluster == NULL))
				{
					delete holder->cluster;
					holder->priority = clusterPriority;

					holder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * clusterID);
					holder->clusterPos = clusterID;
					holder->isGenerated = false;

					std::sort(m_clusters.begin(), m_clusters.end(), clusterPriorityComp);
				}
			}
		}
	}
	
	// generate clusters
	for (ClustersCollection::iterator clusterIt = m_clusters.begin(); clusterIt != m_clusters.end(); ++clusterIt)
	{
		CellClusterHolder& clusterHolder = *clusterIt;

		// dwAssert(clusterHolder.cluster != NULL, "Invalid cluster");
		
		if (clusterHolder.cluster != NULL && !clusterHolder.isGenerated)
		{
			clusterHolder.cluster->initializeMesh();
			clusterHolder.isGenerated = true;

			generatedClustersCount++;

			if (generatedClustersCount >= MaxGenerationPerFrame)
			{
				break ;
			}
		}
	}
}

void CellClusterManager::setCameraNode(const dw::graphics::Node* cameraNode)
{
	m_cameraNode = cameraNode;
}

void CellClusterManager::onCellChanged(Planet& sender, Planet::CellEventArg& eventArg)
{
	Vector3i coords = eventArg.coords;
	Vector3i clusterID;

	if(coords.x >= 0.0 )
		clusterID.x = coords.x / (dw::int32)ClusterSize;
	else
		clusterID.x = (coords.x+1) / (dw::int32)ClusterSize -1;

	if(coords.y >= 0.0 )
		clusterID.y = coords.y / (dw::int32)ClusterSize;
	else
		clusterID.y = (coords.y+1) / (dw::int32)ClusterSize -1;

	if(coords.z >= 0.0 )
		clusterID.z = coords.z / (dw::int32)ClusterSize;
	else
		clusterID.z = (coords.z+1) / (dw::int32)ClusterSize -1;


	CellClusterHolder* holder = findClusterHolder(clusterID);

	Vector3i coordsShift;
	coordsShift.x = coords.x < 0.0f ? coords.x + sender.getRadius() : coords.x;
	coordsShift.y = coords.y < 0.0f ? coords.y + sender.getRadius() : coords.y;
	coordsShift.z = coords.z < 0.0f ? coords.z + sender.getRadius() : coords.z;


	// if we know this cluster invalidate it
	if (holder != NULL)
	{
		delete holder->cluster;
		holder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * clusterID);
		holder->isGenerated = false;
	}

	// Invalidate neighbors if needed
	if (coordsShift.x % ClusterSize == 0)
	{
		Vector3i neighbourId = clusterID + Vector3i(-1, 0, 0);
		CellClusterHolder* neighbourHolder = findClusterHolder(neighbourId);

		// if we know this cluster invalidate it
		if (neighbourHolder != NULL)
		{
			delete neighbourHolder->cluster;
			neighbourHolder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * neighbourId);
			neighbourHolder->isGenerated = false;
		}
	}
	else if (coordsShift.x % ClusterSize == ClusterSize - 1)
	{
		Vector3i neighbourId = clusterID + Vector3i(1, 0, 0);
		CellClusterHolder* neighbourHolder = findClusterHolder(neighbourId);

		// if we know this cluster invalidate it
		if (neighbourHolder != NULL)
		{
			delete neighbourHolder->cluster;
			neighbourHolder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * neighbourId);
			neighbourHolder->isGenerated = false;
		}
	}

	if (coordsShift.y % ClusterSize == 0)
	{
		Vector3i neighbourId = clusterID + Vector3i(0, -1, 0);
		CellClusterHolder* neighbourHolder = findClusterHolder(neighbourId);

		// if we know this cluster invalidate it
		if (neighbourHolder != NULL)
		{
			delete neighbourHolder->cluster;
			neighbourHolder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * neighbourId);
			neighbourHolder->isGenerated = false;
		}
	}
	else if (coordsShift.y % ClusterSize == ClusterSize - 1)
	{
		Vector3i neighbourId = clusterID + Vector3i(0, 1, 0);
		CellClusterHolder* neighbourHolder = findClusterHolder(neighbourId);

		// if we know this cluster invalidate it
		if (neighbourHolder != NULL)
		{
			delete neighbourHolder->cluster;
			neighbourHolder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * neighbourId);
			neighbourHolder->isGenerated = false;
		}
	}

	if (coordsShift.z % ClusterSize == 0)
	{
		Vector3i neighbourId = clusterID + Vector3i(0, 0, -1);
		CellClusterHolder* neighbourHolder = findClusterHolder(neighbourId);

		// if we know this cluster invalidate it
		if (neighbourHolder != NULL)
		{
			delete neighbourHolder->cluster;
			neighbourHolder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * neighbourId);
			neighbourHolder->isGenerated = false;
		}
	}
	else if (coordsShift.z % ClusterSize == ClusterSize - 1)
	{
		Vector3i neighbourId = clusterID + Vector3i(0, 0, 1);
		CellClusterHolder* neighbourHolder = findClusterHolder(neighbourId);

		// if we know this cluster invalidate it
		if (neighbourHolder != NULL)
		{
			delete neighbourHolder->cluster;
			neighbourHolder->cluster = NEWCELLCLUSTER(ClusterSize, (dw::int32)ClusterSize * neighbourId);
			neighbourHolder->isGenerated = false;
		}
	}
}

/*static*/ bool CellClusterManager::clusterPriorityComp(const CellClusterHolder& lhs, const CellClusterHolder& rhs)
{
	return lhs.priority < rhs.priority;
}

CellClusterManager::CellClusterHolder* CellClusterManager::findClusterHolder(const Vector3i& clusterID, dw::float32 priority)
{
	ClustersCollection::iterator end = m_clusters.end();

	for (ClustersCollection::iterator clusterIt = m_clusters.begin(); clusterIt != end; ++clusterIt)
	{
		if (clusterIt->clusterPos == clusterID)
		{
			return &(*clusterIt);
		}
	}

	typedef std::reverse_iterator<ClustersCollection::iterator> revIterT;

	CellClusterHolder& lastCluster = m_clusters.back();

	// if there is a free cluster it must be the last one
	if (lastCluster.cluster == NULL)
	{
		return &lastCluster;
	}

	// if there is a cluster with low priority it must be the last one
	if (lastCluster.priority > priority)
	{
		return &lastCluster;
	}

	return NULL;
}

CellClusterManager::CellClusterHolder* CellClusterManager::findClusterHolder(const Vector3i& clusterID)
{
	ClustersCollection::iterator end = m_clusters.end();

	for (ClustersCollection::iterator clusterIt = m_clusters.begin(); clusterIt != end; ++clusterIt)
	{
		if (clusterIt->clusterPos == clusterID)
		{
			return &(*clusterIt);
		}
	}

	return NULL;
}

	} // namespace render
} // namespace infinity